/*
 * (C) Copyright 2006-2010 Nuxeo SAS (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     bstefanescu
 */
package org.nuxeo.gwt.habyt.demo.client.widgets;

import java.io.Serializable;
import java.util.List;

import org.nuxeo.gwt.habyt.app.client.Action;
import org.nuxeo.gwt.habyt.app.client.actions.ViewAction;
import org.nuxeo.gwt.habyt.demo.client.Document;
import org.nuxeo.gwt.habyt.invocation.client.Actions;
import org.nuxeo.gwt.habyt.invocation.client.DataModel;
import org.nuxeo.gwt.habyt.invocation.client.Invocation;
import org.nuxeo.gwt.habyt.invocation.client.InvocationCallback;
import org.nuxeo.gwt.habyt.invocation.client.InvocationResponse;
import org.nuxeo.gwt.habyt.invocation.client.Invoker;
import org.nuxeo.gwt.habyt.invocation.client.ObjectRef;
import org.nuxeo.gwt.habyt.invocation.client.Referenceable;
import org.nuxeo.gwt.habyt.invocation.client.events.EventBus;
import org.nuxeo.gwt.habyt.invocation.client.events.NotificationEvent;
import org.nuxeo.gwt.habyt.invocation.client.events.NotificationEventHandler;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.Widget;

/**
 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
 * 
 */
public class DocumentTree extends Tree implements OpenHandler<TreeItem>,
        HasClickHandlers, HasDoubleClickHandlers, NotificationEventHandler {

    /**
     * root reference
     */
    protected Referenceable root;

    protected HandlerRegistration hreg;

    public DocumentTree(Referenceable root) {
        this.root = root;
        sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.ONCONTEXTMENU);
        addOpenHandler(this);
        addDoubleClickHandler(new DoubleClickHandler() {
            @Override
            public void onDoubleClick(DoubleClickEvent event) {
                Referenceable ref = getSelectedObject();
                if (ref != null) {
                    Action<Referenceable> action = new ViewAction(ref);
                    // TODO use target from element binding
                    action.setTarget("editor");
                    action.execute(DocumentTree.this);
                }
            }
        });
    }

    @Override
    public HandlerRegistration addClickHandler(ClickHandler handler) {
        return addDomHandler(handler, ClickEvent.getType());
    }

    @Override
    public HandlerRegistration addDoubleClickHandler(DoubleClickHandler handler) {
        return addDomHandler(handler, DoubleClickEvent.getType());
    }

    @Override
    protected void onLoad() {
        asyncGetChildren(null, root);
        hreg = EventBus.get().addNotificationHandler(this);
    }

    @Override
    protected void onUnload() {
        if (hreg != null) {
            hreg.removeHandler();
        }
    }

    public Referenceable getRoot() {
        return root;
    }

    @Override
    public void onOpen(OpenEvent<TreeItem> event) {
        TreeItem item = event.getTarget();
        if (item.getChildCount() == 1
                && item.getChild(0).getUserObject() == null) {
            // Close the item immediately
            item.setState(false, false);

            // fill children
            asyncGetChildren(item, (Referenceable) item.getUserObject());
        }
    }

    public Referenceable getSelectedObject() {
        TreeItem item = getSelectedItem();
        return item != null ? (Referenceable) item.getUserObject() : null;
    }

    protected void asyncGetChildren(final TreeItem parent,
            final Referenceable parentRef) {
        if (parentRef == null) {
            return;
        }
        Invocation invocation = new Invocation(parentRef, Actions.CHILDREN);
        Invoker.invoke(invocation, new InvocationCallback() {
            @SuppressWarnings("unchecked")
            @Override
            public void onSuccess(InvocationResponse result) {
                // Remove the temporary item when we finish loading
                if (parent != null) {
                    parent.getChild(0).remove();
                }

                List<Document> children = (List<Document>) result.getContent();
                if (children != null) {
                    for (DataModel data : children) {
                        TreeItem item = parent == null ? addItem(getItemWidget(data))
                                : parent.addItem(getItemWidget(data));
                        item.setUserObject(data);
                        if (isFolder(data)) {
                            item.addItem(createLoadingItem());
                        }
                    }
                }

                // Reopen the item
                if (parent != null) {
                    parent.setState(true, false);
                }
            }

            @Override
            public void onFailure(Throwable caught) {
                Window.alert("Failed getting children: " + caught.getMessage());
            }
        });
    }

    protected String getItemWidget(DataModel model) {
        String icon = model.getIcon();
        if (icon != null) {
            return "<img border='0' align='top' src='" + icon + "' />&nbsp;"
                    + model.getTitle();
        } else {
            return model.getTitle();
        }
    }

    protected boolean isFolder(DataModel model) {
        return true;
    }

    protected Widget createLoadingItem() {
        return new HTML("Loading ...");
    }

    public TreeItem findItem(String id) {
        for (int i = 0, len = getItemCount(); i < len; i++) {
            TreeItem item = findItem(getItem(i), id);
            if (item != null) {
                return item;
            }
        }
        return null;
    }

    public TreeItem findItem(TreeItem item, String id) {
        DataModel data = (DataModel) item.getUserObject();
        if (data != null && id.equals(data.getRef().getId())) {
            return item;
        }
        int cnt = item.getChildCount();
        if (cnt == 0 || (cnt == 1 && item.getChild(0).getUserObject() == null)) {
            return null;
        }
        for (int i = 0; i < cnt; i++) {
            TreeItem item2 = findItem(item.getChild(i), id);
            if (item2 != null) {
                return item2;
            }
        }
        return null;
    }

    @Override
    public void onNotification(NotificationEvent event) {
        ObjectRef ref = event.getNotification().getTarget();
        if ("doc".equals(ref.getType())) {
            TreeItem item = findItem(ref.getId());
            if (item != null) {
                boolean selectItem = event.getNotification() instanceof InvocationResponse;
                update(item, event.getNotification().getAction(),
                        event.getNotification().getContent(), selectItem);
            }
        }
    }

    protected boolean hasLoadingItem(TreeItem item) {
        return item.getChildCount() == 1
                && item.getChild(0).getUserObject() == null;
    }

    public void update(TreeItem item, String action, Serializable data,
            boolean selectItem) {
        if ("create".equals(action)) {
            if (hasLoadingItem(item)) {
                item.removeItems();
            }
            TreeItem child = item.addItem(getItemWidget((DataModel) data));
            child.setUserObject(data);
            if (isFolder((DataModel) data)) {
                child.addItem(createLoadingItem());
            }
            if (selectItem) {
                setSelectedItem(child);
                ensureSelectedItemVisible();
            }
        } else if ("update".equals(action)) {
            String html = getItemWidget((DataModel) data);
            if (html != null) {
                item.setHTML(html);
            }
        } else if ("delete".equals(action)) {
            if (selectItem) {
                setSelectedItem(item.getParentItem());
                item.remove();
                ensureSelectedItemVisible();
            }

        }
    }

}
